#include "Device.h"

#include <ezlogger_headers.hpp>

namespace Crows3D
{
   Device::Device(void)
   {
      EZLOGGERSTREAM << "Device Constructor" << std::endl;

      swapChain     = NULL;
      device        = NULL;
      deviceContext = NULL;
      renderTarget  = NULL;

      rasterState = 0;

      depthStencilBuffer = 0;
      depthStencilState = 0;
      depthStencilView = 0;

   }

   Device::~Device(void)
   {
      EZLOGGERSTREAM << "Device Destructor" << std::endl;

      // Make sure we restore the orignal screen
      if (swapChain)
      {
         swapChain->SetFullscreenState(false, NULL);    // switch to windowed mode
      }

	   if(rasterState)
	   {
		   rasterState->Release();
		   rasterState = 0;
	   }

      if(alphaEnableBlendingState)
	   {
		   alphaEnableBlendingState->Release();
		   alphaEnableBlendingState = 0;
	   }
      if(alphaDisableBlendingState)
	   {
		   alphaDisableBlendingState->Release();
		   alphaDisableBlendingState = 0;
	   }



      if(depthStencilView)
	   {
		   depthStencilView->Release();
		   depthStencilView = 0;
	   }
      if(depthStencilDisabledState)
	   {
		   depthStencilDisabledState->Release();
		   depthStencilDisabledState = 0;
	   }


	   if(depthStencilState)
	   {
		   depthStencilState->Release();
		   depthStencilState = 0;
	   }

	   if(depthStencilBuffer)
	   {
		   depthStencilBuffer->Release();
		   depthStencilBuffer = 0;
	   }



      if(deviceContext) 
      {
         deviceContext->ClearState();
      }

      if(renderTarget) 
      {
         renderTarget->Release();
         renderTarget = 0;
      }



      if(deviceContext)
      {
         deviceContext->ClearState();
         deviceContext->Release();
         deviceContext = 0;
      }

      if(device) 
      {
         device->Release();
         device = 0;
      }      
      
      if(swapChain)
      {
         swapChain->Release();
         swapChain = 0;
      }
   }


   bool Device::Initialize(HWND hwnd, GraphicsSetupParameters & parameters)
   {
      // Everything we'll need...
      DXGI_SWAP_CHAIN_DESC swapChainDesc;                                          // Store all our parameters
      UINT                 createDeviceFlags = 0;                       // Any special device flags
      D3D_FEATURE_LEVEL    featureLevel      = D3D_FEATURE_LEVEL_11_0;  // D3D 11!
      ID3D11Texture2D*     pBackBuffer       = NULL;                    // Handle to the backbuffer
      D3D11_VIEWPORT       vp;                                          // 

      EZLOGGERSTREAM << "Initializing Direct3D" << std::endl;


      // Make sure to use a debug render device for testing
      #ifdef _DEBUG
         createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
      #endif
         
      
      // Clear the struct out
      memset(&swapChainDesc, 0, sizeof(swapChainDesc));

      // Set our parameters
      swapChainDesc.BufferCount = 1;                                                     // Single Backbuffer
      swapChainDesc.BufferDesc.Width = parameters.width;                                 // Specified Width
      swapChainDesc.BufferDesc.Height = parameters.height;                               // specified Height
      swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;                      // 32-bit colors
      
      if (parameters.vsync)
      {
         swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;                            // 60/1 = 60 hertz
         swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;                              
      }
      else
      {
         swapChainDesc.BufferDesc.RefreshRate.Numerator = 0;                             // 0/1 = no vsync
         swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;                              
      }
      
      swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;                       // This is going to the monitor
      swapChainDesc.OutputWindow = hwnd;                                                 // Our window to render to
      swapChainDesc.SampleDesc.Count = 1;    //4;                                        // No multisampling
      swapChainDesc.SampleDesc.Quality = 0;//D3D11_STANDARD_MULTISAMPLE_PATTERN;         // No AA
      swapChainDesc.Windowed = parameters.windowed;                                                 // Are using a window or taking over the whole screen?
	   swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;  // Set the scan line ordering and scaling to unspecified.
	   swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
      swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;                               // Discard the back buffer contents after presenting.
      swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;                      // Allow switching between windowed and fullscreen
      
      // Actually create the d3d device and swap chain
      if(FAILED(D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 
                                              createDeviceFlags, &featureLevel, 
                                              1, D3D11_SDK_VERSION, &swapChainDesc, 
                                              &swapChain, &device, NULL, 
                                              &deviceContext)))
      {
         EZLOGGERSTREAM << "ERROR: Failed to create Direct3D Device and Swap Chain" << std::endl;
         return false;
      }



      // Create the render target
      if(FAILED(swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer)))
      {
         EZLOGGERSTREAM << "ERROR: Failed to get backbuffer from swap chain" << std::endl;
         return false;
      }
      if (FAILED(device->CreateRenderTargetView(pBackBuffer, NULL, &renderTarget)))
      {
         EZLOGGERSTREAM << "ERROR: Failed to Create Render Target View" << std::endl;
         return false;
      }
      pBackBuffer->Release();
      pBackBuffer = NULL;

      


      // Set up depth buffer
      D3D11_TEXTURE2D_DESC depthBufferDesc;
	   D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
	   D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;


      // Initialize the description of the depth buffer.
	   ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));

	   // Set up the description of the depth buffer.
	   depthBufferDesc.Width = parameters.width;
	   depthBufferDesc.Height = parameters.height;
	   depthBufferDesc.MipLevels = 1;
	   depthBufferDesc.ArraySize = 1;
	   depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; //DXGI_FORMAT_D16_UNORM; 
	   depthBufferDesc.SampleDesc.Count = 1;
	   depthBufferDesc.SampleDesc.Quality = 0;
	   depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	   depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	   depthBufferDesc.CPUAccessFlags = 0;
	   depthBufferDesc.MiscFlags = 0;

      // Create the texture for the depth buffer using the filled out description.
	   if(FAILED(device->CreateTexture2D(&depthBufferDesc, NULL, &depthStencilBuffer)))
      {
         EZLOGGERSTREAM << "ERROR: Failed to create Depth stencil texture" << std::endl;
		   return false;
	   }


      // Our "enabled" depth stencil
      {
         // Initialize the description of the stencil state.
	      ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

	      // Set up the description of the stencil state.
	      depthStencilDesc.DepthEnable = true;
	      depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	      depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

	      depthStencilDesc.StencilEnable = false;
	      depthStencilDesc.StencilReadMask = 0xFF;
	      depthStencilDesc.StencilWriteMask = 0xFF;

	      // Stencil operations if pixel is front-facing.
	      depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	      depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	      // Stencil operations if pixel is back-facing.
	      depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	      depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	      // Create the depth stencil state.
	      if(FAILED(device->CreateDepthStencilState(&depthStencilDesc, &depthStencilState)))
	      {
            EZLOGGERSTREAM << "ERROR: Failed to create \'enabled\' depth stencil state" << std::endl;
		      return false;
	      }
      }

      // Our "disabled" depth stencil
      {
         // Initialize the description of the stencil state.
	      ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc));

	      // Set up the description of the stencil state.
	      depthStencilDesc.DepthEnable = false;
	      depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
	      depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

	      depthStencilDesc.StencilEnable = false;
	      depthStencilDesc.StencilReadMask = 0xFF;
	      depthStencilDesc.StencilWriteMask = 0xFF;

	      // Stencil operations if pixel is front-facing.
	      depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR;
	      depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	      // Stencil operations if pixel is back-facing.
	      depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR;
	      depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
	      depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

	      // Create the depth stencil state.
	      if(FAILED(device->CreateDepthStencilState(&depthStencilDesc, &depthStencilDisabledState)))
	      {
            EZLOGGERSTREAM << "ERROR: Failed to create \'disabled\' depth stencil state" << std::endl;
		      return false;
	      }
      }

	   // Set the depth stencil state.
	   deviceContext->OMSetDepthStencilState(depthStencilState, 1);

	   // Initialize the depth stencil view.
	   ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));

	   // Set up the depth stencil view description.
	   depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
      depthStencilViewDesc.Flags = 0;
	   depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	   depthStencilViewDesc.Texture2D.MipSlice = 0;

	   // Create the depth stencil view.
	   if(FAILED(device->CreateDepthStencilView(depthStencilBuffer, &depthStencilViewDesc, &depthStencilView)))
	   {
         EZLOGGERSTREAM << "ERROR: Failed to create depth stencil view" << std::endl;
		   return false;
	   }

      // Create and clear the blend state description.
      D3D11_BLEND_DESC blendStateDescription;
	   ZeroMemory(&blendStateDescription, sizeof(D3D11_BLEND_DESC));

	   // Create an alpha enabled blend state description.
	   blendStateDescription.RenderTarget[0].BlendEnable = TRUE;
      blendStateDescription.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
      blendStateDescription.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
      blendStateDescription.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
      blendStateDescription.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE;
      blendStateDescription.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
      blendStateDescription.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
      blendStateDescription.RenderTarget[0].RenderTargetWriteMask = 0x0f;

	   // Create the blend state using the description.
	   if(FAILED(device->CreateBlendState(&blendStateDescription, &alphaEnableBlendingState)))
	   {
         EZLOGGERSTREAM << "ERROR: Failed to create alpha blending \'enabled\' state" << std::endl;
		   return false;
	   }

      // Modify the description to create an alpha disabled blend state description.
	   blendStateDescription.RenderTarget[0].BlendEnable = FALSE;

	   // Create the blend state using the description.
	   if(FAILED(device->CreateBlendState(&blendStateDescription, &alphaDisableBlendingState)))
	   {
         EZLOGGERSTREAM << "ERROR: Failed to create alpha blending \'diabled\' state" << std::endl;
		   return false;
	   }










      // Bind the render target view and depth stencil buffer to the output render pipeline.
      deviceContext->OMSetRenderTargets(1, &renderTarget, depthStencilView);


      // set our rasterizer options
      D3D11_RASTERIZER_DESC rasterDesc;
	   rasterDesc.AntialiasedLineEnable = false;
	   rasterDesc.CullMode = D3D11_CULL_BACK;    // Cull back faces
	   rasterDesc.DepthBias = 0;
	   rasterDesc.DepthBiasClamp = 0.0f;
	   rasterDesc.DepthClipEnable = true;
	   rasterDesc.FillMode = D3D11_FILL_SOLID;   // Fill solid, not wireframe
	   rasterDesc.FrontCounterClockwise = false;
	   rasterDesc.MultisampleEnable = false;
	   rasterDesc.ScissorEnable = false;
	   rasterDesc.SlopeScaledDepthBias = 0.0f;

	   // Create the rasterizer state from the description we just filled out.
	   if (FAILED(device->CreateRasterizerState(&rasterDesc, &rasterState)))
	   {
         EZLOGGERSTREAM << "ERROR: Failed to create rasterizer state" << std::endl;
		   return false;
	   }

	   // Now set the rasterizer state.
	   deviceContext->RSSetState(rasterState);




      // Set up the viewport
      memset(&vp, 0, sizeof(vp));

      vp.Width = (float)parameters.width;
      vp.Height = (float)parameters.height;
      vp.MinDepth = 0.0f;
	   vp.MaxDepth = 1.0f;
      vp.TopLeftX = 0.0f;
      vp.TopLeftY = 0.0f;

      deviceContext->RSSetViewports(1, &vp);




      // Report Success
      return true;
   }


   void Device::ClearBackbuffer(float color[4])
   {
      //EZLOGGERSTREAM << "Clearing Back Buffer" << std::endl;
      deviceContext->ClearRenderTargetView(renderTarget, color);
   }

   void Device::ClearDepthStencil(float depth, unsigned int stencil)
   {
      //EZLOGGERSTREAM << "Clearing depth stencil" << std::endl;
      deviceContext->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, depth, stencil);
   }



   void Device::Swap(void)
   {
      //EZLOGGERSTREAM << "Swapping backbuffer" << std::endl;
      swapChain->Present( 0, 0 );
   }




   ID3D11Device* Device::GetDevice()
   {
      return device;
   }

   ID3D11DeviceContext* Device::GetDeviceContext()
   {
      return deviceContext;
   }


   void Device::EnableDepthBuffer(bool enable)
   {
      //EZLOGGERSTREAM << "Setting Depth Buffer enabled: " << enable << std::endl;
      if (enable)
      {
         // set the enabled state we created
         deviceContext->OMSetDepthStencilState(depthStencilState, 1); // OMSetRenderTargets(1, &renderTarget, depthStencilView);
      }
      else
      {
         // set the disabled state we created
         deviceContext->OMSetDepthStencilState(depthStencilDisabledState, 1); // OMSetRenderTargets(1, &renderTarget, depthStencilView_Disabled);
      }
      
   }

   void Device::EnableAlphaBlending(bool enable)
   {
      float blendFactor[4];

      //EZLOGGERSTREAM << "Setting Alpha Blending enabled: " << enable << std::endl;

      // Setup the blend factor.
	   blendFactor[0] = 0.0f;
	   blendFactor[1] = 0.0f;
	   blendFactor[2] = 0.0f;
      blendFactor[3] = 0.0f;


      if (enable)
      {
	      // Turn on the alpha blending.
	      deviceContext->OMSetBlendState(alphaEnableBlendingState, blendFactor, 0xffffffff);
      }
      else
      {	
	      // Turn on the alpha blending.
	      deviceContext->OMSetBlendState(alphaDisableBlendingState, blendFactor, 0xffffffff);
      }
   }
}